#include "world.h"
#include "monkey.h"
#include "coconut.h"
#include "banana.h"


World World::world;

// moves all the objects & processes coconut-hits
void World::updateWorld()
{
    for(WorldObject* obj : objects)
    {
        if(obj->getIsMovable())
        {
            obj->updatePosition();

            for(WorldObject* o : objects)
            {
                if (obj->getType() == COCONUT
                        && o->getIsHitable() == true
                        && obj->getId() != o->getId()
                        && obj->getRect().intersects(o->getRect()))
                {
                    Coconut* coco = dynamic_cast<Coconut*>(obj);

                    if (o->getType() == BANANA)
                    {
                        mObs->increasedScoreInModel(coco->getOwner(), dynamic_cast<Banana*>(o)->getScoreValue());
                        o->setIsHitable(false);
                        o->setDestroyLater(true);
                    }
                    else if (o->getType() == MONKEY && coco->getOwner() != dynamic_cast<Monkey*>(o)->getId())
                    {
                        o->setIsHitable(false);
                        o->setIsMovable(true);
                        mObs->setMonkeyToFalling(std::vector<PlayerType>{HOST,CLIENT}.at(o->getId()), true);
                    }
                }
            }

        }
    }

    // figure out which objects to destroy
    for(WorldObject* obj : objects)
    {
        if (obj->getDestroyLater() && obj->getType() != MONKEY)
        {
            world.destroyById(obj->getId());
        }
    }
}

void World::setMonkeyMoveState(PlayerType pl, bool moveable, Direction dir)
{
    Monkey* m = dynamic_cast<Monkey*>(getById(pl));
    m->setIsMovable(moveable);
    m->setDirection(dir);
}

void World::createMonkeys()
{
    Monkey* m1 = new Monkey(LEFT);
    world.addObject(m1);

    Monkey* m2 = new Monkey(RIGHT);
    world.addObject(m2);
}

void World::createCoconut(PlayerType pl)
{
    Coconut* c = new Coconut(pl, getById(pl)->getTop() + COCO_X_OFFSET);
    world.addObject(c);
}

void World::createBanana()
{
    Banana* b = new Banana();
    world.addObject(b);
}

// finds a target for AI and returns it (returns null if there is none)
WorldObject* World::findMonkeyAITarget()
{
    vector<WorldObject*> targets;
    Monkey* AI = dynamic_cast<Monkey*>(objects.at(1));

    for (WorldObject* obj : objects)
    {
        if (obj->getIsHitable())
        {
            if (obj->getType() == MONKEY && obj->getId() != 1)
            {
                targets.push_back(obj);
            }
            else if (obj->getType() == BANANA)
            {
                if (_difficulty_ != EASY) // also go after banana that aren't reachable any more or not yet reachable
                {
                    double ban_low_dist = MONKEY_LOWEST_POINT + COCO_Y_OFFSET - obj->getTop() - abs(AI->getSpeed());
                    double ban_high_dist = MONKEY_HEIGHEST_POINT + COCO_Y_OFFSET - obj->getTop();
                    double ban_speed = abs(obj->getSpeed());
                    double coc_dist = AI->getRight() - COCO_X_OFFSET - COCO_WIDTH - obj->getLeft();
                    double coc_speed = abs(COCO_SPEED.at(_difficulty_));
                    double mon_low_dist = MONKEY_LOWEST_POINT - AI->getTop();
                    double mon_high_dist = AI->getTop() - MONKEY_HEIGHEST_POINT;
                    double mon_speed = abs(AI->getSpeed()) * 2;

                    double remainingLoadTime = std::max(0, AI->getMaxLoadTime() - AI->getCocoTime()) /
                            MAIN_TIMER_UPDATE_INTERVAL;

                    double monkeyGetDownReadyTime = std::max(remainingLoadTime, mon_low_dist / mon_speed);
                    double monkeyGetUpReadyTime = std::max(remainingLoadTime, mon_high_dist / mon_speed);

                    bool bananaTooFarDown = (ban_low_dist / ban_speed < monkeyGetDownReadyTime + coc_dist / coc_speed);
                    bool bananaTooFarUp = (ban_high_dist / ban_speed > monkeyGetUpReadyTime + coc_dist / coc_speed);

                    if (!bananaTooFarDown && !bananaTooFarUp)
                    {
                        targets.push_back(obj);
                    }
                }
                else
                {
                    targets.push_back(obj);
                }

            }
        }
    }

    if (targets.size() == 0) {
        return NULL;
    } else if (targets.at(0)->getType() == MONKEY &&
               AI_AIM_FOR_OPPONENT.at(_difficulty_) > rand() % 100) {  // shoot at other monkey from time to time
        return targets.at(0);
    } else if (targets.size() > 1) {
        return targets.at(1 + rand() % (targets.size() - 1));
    } else {
        return NULL;
    }
}

void World::doMonkeyAIMove()
{
    Monkey* AI = dynamic_cast<Monkey*>(objects.at(1));
    if (!AI->getIsHitable()) { return; }    // monkey is falling... don't do anything

    WorldObject* curTarget = getById(AI->getTargetId());

    if (curTarget == NULL || !curTarget->getIsHitable())    // find new target
    {
        curTarget = findMonkeyAITarget();
        if (curTarget == NULL) {        // no target found -> just move AI up
            AI->setTargetId(-1);
            AI->setDirection(UP);
            AI->setIsMovable(true);
            AI->increaseCocoTime(MAIN_TIMER_UPDATE_INTERVAL);
            return;
        }

        AI->setTargetId(curTarget->getId());
    }

    int targetY;
    int variance;   // the precision of the AI shots

    if (curTarget->getType() == MONKEY) {
        targetY = curTarget->getTop();
        variance = abs(AI->getSpeed()) * 3;
    } else if (curTarget->getType() == BANANA) {
        targetY = curTarget->getTop() + 1.0 * curTarget->getSpeed() *
                    (AI->getRight() - COCO_X_OFFSET - COCO_WIDTH - curTarget->getLeft()) /   // distance the coconut has to fly
                    abs(COCO_SPEED.at(_difficulty_)) - COCO_Y_OFFSET;
        variance = abs(AI->getSpeed());
    }


    if (abs(targetY - AI->getTop()) < variance) // shoot coconut if possible
    {
        if (AI->getCocoTime() >= AI->getMaxLoadTime())
        {
            mObs->monkeyAIShootsCoconut();
            AI->resetCocoTime();
            AI->setMaxLoadTime();
            AI->setTargetId(-1);
            AI->setIsMovable(false);
        }
        else  // too little time since last coconut-shot
        {
            if (targetY > AI->getTop() && curTarget->getSpeed() > 0) {
                AI->setDirection(DOWN);
                AI->setIsMovable(true);
            } else if (targetY < AI->getTop() && curTarget->getSpeed() < 0) {
                AI->setDirection(UP);
                AI->setIsMovable(true);
            } else {
                AI->setIsMovable(false);
            }

            AI->increaseCocoTime(MAIN_TIMER_UPDATE_INTERVAL);
        }

    }
    else
    {
        if (targetY >= AI->getTop()) {
            AI->setDirection(DOWN);
        } else {
            AI->setDirection(UP);
        }

        AI->setIsMovable(true);
        AI->increaseCocoTime(MAIN_TIMER_UPDATE_INTERVAL);
    }

}

void World::addObject(WorldObject* o)
{
    o->attachObserver(mObs);
    mObs->createdInModel(o->getId(), o->getType(), o->getImage(), o->getLeft(), o->getTop());
    objects.push_back(o);
}

// returns a worldobject with <id>
WorldObject* World::getById(int id)
{
    for (size_t i = 0; i < objects.size(); ++i)
    {
        WorldObject* obj = objects.at(i);
        if (obj->getId() == id) {
            return obj;
        }
    }
    return NULL;
}

// Removes worldobject with <id> from objects
void World::destroyById(int id)
{
    for (size_t i = 0; i < objects.size(); ++i)
    {
        WorldObject* obj = objects.at(i);
        if (obj->getId() == id) {
            delete obj;
            objects.erase(objects.begin() + i);
        }
    }
}

// Resets world
void World::reset()
{
    WorldObject::resetId();

    for (WorldObject* obj : objects)
    {
        delete obj;
    }
    objects.clear();
}


World::~World()
{
    reset();
}
